1 package org.apache.solr.handler;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import java.io.IOException;
22 import java.lang.invoke.MethodHandles;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.concurrent.Callable;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.Future;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.locks.Lock;
40 import java.util.concurrent.locks.ReentrantLock;
41
42 import com.google.common.collect.ImmutableSet;
43 import org.apache.solr.client.solrj.SolrClient;
44 import org.apache.solr.client.solrj.SolrRequest;
45 import org.apache.solr.client.solrj.SolrResponse;
46 import org.apache.solr.client.solrj.impl.HttpSolrClient;
47 import org.apache.solr.cloud.ZkController;
48 import org.apache.solr.cloud.ZkSolrResourceLoader;
49 import org.apache.solr.common.SolrException;
50 import org.apache.solr.common.cloud.ClusterState;
51 import org.apache.solr.common.cloud.Replica;
52 import org.apache.solr.common.cloud.Slice;
53 import org.apache.solr.common.cloud.ZkNodeProps;
54 import org.apache.solr.common.params.CommonParams;
55 import org.apache.solr.common.params.MapSolrParams;
56 import org.apache.solr.common.params.ModifiableSolrParams;
57 import org.apache.solr.common.params.SolrParams;
58 import org.apache.solr.common.util.ContentStream;
59 import org.apache.solr.common.util.ExecutorUtil;
60 import org.apache.solr.common.util.NamedList;
61 import org.apache.solr.common.util.StrUtils;
62 import org.apache.solr.common.util.Utils;
63 import org.apache.solr.core.ConfigOverlay;
64 import org.apache.solr.core.ImplicitPlugins;
65 import org.apache.solr.core.PluginInfo;
66 import org.apache.solr.core.RequestParams;
67 import org.apache.solr.core.SolrConfig;
68 import org.apache.solr.core.SolrCore;
69 import org.apache.solr.core.SolrResourceLoader;
70 import org.apache.solr.request.SolrQueryRequest;
71 import org.apache.solr.request.SolrRequestHandler;
72 import org.apache.solr.response.SolrQueryResponse;
73 import org.apache.solr.schema.SchemaManager;
74 import org.apache.solr.util.CommandOperation;
75 import org.apache.solr.util.DefaultSolrThreadFactory;
76 import org.apache.solr.util.RTimer;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 import static java.util.Collections.singletonList;
81 import static org.apache.solr.common.util.Utils.makeMap;
82 import static org.apache.solr.common.params.CoreAdminParams.NAME;
83 import static org.apache.solr.common.util.StrUtils.formatString;
84 import static org.apache.solr.core.ConfigOverlay.NOT_EDITABLE;
85 import static org.apache.solr.core.ConfigOverlay.ZNODEVER;
86 import static org.apache.solr.core.ConfigSetProperties.IMMUTABLE_CONFIGSET_ARG;
87 import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS;
88 import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME;
89 import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY;
90 import static org.apache.solr.schema.FieldType.CLASS_NAME;
91
92 public class SolrConfigHandler extends RequestHandlerBase {
93 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
94 public static final String CONFIGSET_EDITING_DISABLED_ARG = "disable.configEdit";
95 public static final boolean configEditing_disabled = Boolean.getBoolean(CONFIGSET_EDITING_DISABLED_ARG);
96 private static final Map<String, SolrConfig.SolrPluginInfo> namedPlugins;
97 private Lock reloadLock = new ReentrantLock(true);
98 private boolean isImmutableConfigSet = false;
99
100 static {
101 Map<String, SolrConfig.SolrPluginInfo> map = new HashMap<>();
102 for (SolrConfig.SolrPluginInfo plugin : SolrConfig.plugins) {
103 if (plugin.options.contains(REQUIRE_NAME) || plugin.options.contains(REQUIRE_NAME_IN_OVERLAY)) {
104 map.put(plugin.getCleanTag().toLowerCase(Locale.ROOT), plugin);
105 }
106 }
107 namedPlugins = Collections.unmodifiableMap(map);
108 }
109
110 @Override
111 public void init(NamedList args) {
112 super.init(args);
113 Object immutable = args.get(IMMUTABLE_CONFIGSET_ARG);
114 isImmutableConfigSet = immutable != null ? Boolean.parseBoolean(immutable.toString()) : false;
115 }
116
117 @Override
118 public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
119
120 setWt(req, CommonParams.JSON);
121 String httpMethod = (String) req.getContext().get("httpMethod");
122 Command command = new Command(req, rsp, httpMethod);
123 if ("POST".equals(httpMethod)) {
124 if (configEditing_disabled || isImmutableConfigSet) {
125 final String reason = configEditing_disabled ? "due to " + CONFIGSET_EDITING_DISABLED_ARG : "because ConfigSet is immutable";
126 throw new SolrException(SolrException.ErrorCode.FORBIDDEN, " solrconfig editing is not enabled " + reason);
127 }
128 try {
129 command.handlePOST();
130 } finally {
131 RequestHandlerUtils.addExperimentalFormatWarning(rsp);
132 }
133 } else {
134 command.handleGET();
135 }
136 }
137
138
139 private class Command {
140 private final SolrQueryRequest req;
141 private final SolrQueryResponse resp;
142 private final String method;
143 private String path;
144 List<String> parts;
145
146 private Command(SolrQueryRequest req, SolrQueryResponse resp, String httpMethod) {
147 this.req = req;
148 this.resp = resp;
149 this.method = httpMethod;
150 path = (String) req.getContext().get("path");
151 if (path == null) path = getDefaultPath();
152 parts = StrUtils.splitSmart(path, '/');
153 if (parts.get(0).isEmpty()) parts.remove(0);
154 }
155
156 private String getDefaultPath() {
157 return "/config";
158 }
159
160 private void handleGET() {
161 if (parts.size() == 1) {
162
163 resp.add("config", getConfigDetails());
164 } else {
165 if (ConfigOverlay.NAME.equals(parts.get(1))) {
166 resp.add(ConfigOverlay.NAME, req.getCore().getSolrConfig().getOverlay().toMap());
167 } else if (RequestParams.NAME.equals(parts.get(1))) {
168 if (parts.size() == 3) {
169 RequestParams params = req.getCore().getSolrConfig().getRequestParams();
170 MapSolrParams p = params.getParams(parts.get(2));
171 Map m = new LinkedHashMap<>();
172 m.put(ZNODEVER, params.getZnodeVersion());
173 if (p != null) {
174 m.put(RequestParams.NAME, makeMap(parts.get(2), p.getMap()));
175 }
176 resp.add(SolrQueryResponse.NAME, m);
177 } else {
178 resp.add(SolrQueryResponse.NAME, req.getCore().getSolrConfig().getRequestParams().toMap());
179 }
180
181 } else {
182 if (ZNODEVER.equals(parts.get(1))) {
183 resp.add(ZNODEVER, Utils.makeMap(
184 ConfigOverlay.NAME, req.getCore().getSolrConfig().getOverlay().getZnodeVersion(),
185 RequestParams.NAME, req.getCore().getSolrConfig().getRequestParams().getZnodeVersion()));
186 boolean checkStale = false;
187 int expectedVersion = req.getParams().getInt(ConfigOverlay.NAME, -1);
188 int actualVersion = req.getCore().getSolrConfig().getOverlay().getZnodeVersion();
189 if (expectedVersion > actualVersion) {
190 log.info("expecting overlay version {} but my version is {}", expectedVersion, actualVersion);
191 checkStale = true;
192 } else if (expectedVersion != -1) {
193 log.info("I already have the expected version {} of config", expectedVersion);
194 }
195 expectedVersion = req.getParams().getInt(RequestParams.NAME, -1);
196 actualVersion = req.getCore().getSolrConfig().getRequestParams().getZnodeVersion();
197 if (expectedVersion > actualVersion) {
198 log.info("expecting params version {} but my version is {}", expectedVersion, actualVersion);
199 checkStale = true;
200 } else if (expectedVersion != -1) {
201 log.info("I already have the expected version {} of params", expectedVersion);
202 }
203 if (checkStale && req.getCore().getResourceLoader() instanceof ZkSolrResourceLoader) {
204 new Thread(SolrConfigHandler.class.getSimpleName() + "-refreshconf") {
205 @Override
206 public void run() {
207 if (!reloadLock.tryLock()) {
208 log.info("Another reload is in progress . Not doing anything");
209 return;
210 }
211 try {
212 log.info("Trying to update my configs");
213 SolrCore.getConfListener(req.getCore(), (ZkSolrResourceLoader) req.getCore().getResourceLoader()).run();
214 } catch (Exception e) {
215 log.error("Unable to refresh conf ", e);
216 } finally {
217 reloadLock.unlock();
218 }
219 }
220 }.start();
221 } else {
222 log.info("checkStale {} , resourceloader {}", checkStale, req.getCore().getResourceLoader().getClass().getName());
223 }
224
225 } else {
226 Map<String, Object> m = getConfigDetails();
227 resp.add("config", makeMap(parts.get(1), m.get(parts.get(1))));
228 }
229 }
230 }
231 }
232
233 private Map<String, Object> getConfigDetails() {
234 Map<String, Object> map = req.getCore().getSolrConfig().toMap();
235 Map reqHandlers = (Map) map.get(SolrRequestHandler.TYPE);
236 if (reqHandlers == null) map.put(SolrRequestHandler.TYPE, reqHandlers = new LinkedHashMap<>());
237 List<PluginInfo> plugins = ImplicitPlugins.getHandlers(req.getCore());
238 for (PluginInfo plugin : plugins) {
239 if (SolrRequestHandler.TYPE.equals(plugin.type)) {
240 if (!reqHandlers.containsKey(plugin.name)) {
241 reqHandlers.put(plugin.name, plugin.toMap());
242 }
243 }
244 }
245 return map;
246 }
247
248
249 private void handlePOST() throws IOException {
250 List<CommandOperation> ops = CommandOperation.readCommands(req.getContentStreams(), resp);
251 if (ops == null) return;
252 try {
253 for (; ; ) {
254 ArrayList<CommandOperation> opsCopy = new ArrayList<>(ops.size());
255 for (CommandOperation op : ops) opsCopy.add(op.getCopy());
256 try {
257 if (parts.size() > 1 && RequestParams.NAME.equals(parts.get(1))) {
258 RequestParams params = RequestParams.getFreshRequestParams(req.getCore().getResourceLoader(), req.getCore().getSolrConfig().getRequestParams());
259 handleParams(opsCopy, params);
260 } else {
261 ConfigOverlay overlay = SolrConfig.getConfigOverlay(req.getCore().getResourceLoader());
262 handleCommands(opsCopy, overlay);
263 }
264 break;
265 } catch (ZkController.ResourceModifiedInZkException e) {
266
267 log.info("Race condition, the node is modified in ZK by someone else " + e.getMessage());
268 }
269 }
270 } catch (Exception e) {
271 resp.setException(e);
272 resp.add(CommandOperation.ERR_MSGS, singletonList(SchemaManager.getErrorStr(e)));
273 }
274
275 }
276
277
278 private void handleParams(ArrayList<CommandOperation> ops, RequestParams params) {
279 for (CommandOperation op : ops) {
280 switch (op.name) {
281 case SET:
282 case UPDATE: {
283 Map<String, Object> map = op.getDataMap();
284 if (op.hasError()) break;
285
286 for (Map.Entry<String, Object> entry : map.entrySet()) {
287
288 Map val = null;
289 String key = entry.getKey();
290 if (key == null || key.trim().isEmpty()) {
291 op.addError("null key ");
292 continue;
293 }
294 key = key.trim();
295 String err = validateName(key);
296 if (err != null) {
297 op.addError(err);
298 continue;
299 }
300
301 try {
302 val = (Map) entry.getValue();
303 } catch (Exception e1) {
304 op.addError("invalid params for key : " + key);
305 continue;
306 }
307
308 if (val.containsKey("")) {
309 op.addError("Empty keys are not allowed in params");
310 continue;
311 }
312
313 MapSolrParams old = params.getParams(key);
314 if (op.name.equals(UPDATE)) {
315 LinkedHashMap m = new LinkedHashMap(old.getMap());
316 m.putAll(val);
317 val = m;
318 }
319 params = params.setParams(key, val);
320
321 }
322 break;
323
324 }
325 case "delete": {
326 List<String> name = op.getStrs(CommandOperation.ROOT_OBJ);
327 if (op.hasError()) break;
328 for (String s : name) {
329 if (params.getParams(s) == null) {
330 op.addError(formatString("can't delete . No such params ''{0}'' exist", s));
331 }
332 params = params.setParams(s, null);
333 }
334 }
335 }
336 }
337
338
339 List errs = CommandOperation.captureErrors(ops);
340 if (!errs.isEmpty()) {
341 resp.add(CommandOperation.ERR_MSGS, errs);
342 return;
343 }
344
345 SolrResourceLoader loader = req.getCore().getResourceLoader();
346 if (loader instanceof ZkSolrResourceLoader) {
347 ZkSolrResourceLoader zkLoader = (ZkSolrResourceLoader) loader;
348 if (ops.isEmpty()) {
349 ZkController.touchConfDir(zkLoader);
350 } else {
351 log.info("persisting params version : {}", params.toMap());
352 int latestVersion = ZkController.persistConfigResourceToZooKeeper(zkLoader,
353 params.getZnodeVersion(),
354 RequestParams.RESOURCE,
355 params.toByteArray(), true);
356 waitForAllReplicasState(req.getCore().getCoreDescriptor().getCloudDescriptor().getCollectionName(),
357 req.getCore().getCoreDescriptor().getCoreContainer().getZkController(),
358 RequestParams.NAME,
359 latestVersion, 30);
360 }
361
362 } else {
363 SolrResourceLoader.persistConfLocally(loader, RequestParams.RESOURCE, params.toByteArray());
364 req.getCore().getSolrConfig().refreshRequestParams();
365 }
366
367 }
368
369 private void handleCommands(List<CommandOperation> ops, ConfigOverlay overlay) throws IOException {
370 for (CommandOperation op : ops) {
371 switch (op.name) {
372 case SET_PROPERTY:
373 overlay = applySetProp(op, overlay);
374 break;
375 case UNSET_PROPERTY:
376 overlay = applyUnset(op, overlay);
377 break;
378 case SET_USER_PROPERTY:
379 overlay = applySetUserProp(op, overlay);
380 break;
381 case UNSET_USER_PROPERTY:
382 overlay = applyUnsetUserProp(op, overlay);
383 break;
384 default: {
385 List<String> pcs = StrUtils.splitSmart(op.name.toLowerCase(Locale.ROOT), '-');
386 if (pcs.size() != 2) {
387 op.addError(formatString("Unknown operation ''{0}'' ", op.name));
388 } else {
389 String prefix = pcs.get(0);
390 String name = pcs.get(1);
391 if (cmdPrefixes.contains(prefix) && namedPlugins.containsKey(name)) {
392 SolrConfig.SolrPluginInfo info = namedPlugins.get(name);
393 if ("delete".equals(prefix)) {
394 overlay = deleteNamedComponent(op, overlay, info.getCleanTag());
395 } else {
396 overlay = updateNamedPlugin(info, op, overlay, prefix.equals("create") || prefix.equals("add"));
397 }
398 } else {
399 op.unknownOperation();
400 }
401 }
402 }
403 }
404 }
405 List errs = CommandOperation.captureErrors(ops);
406 if (!errs.isEmpty()) {
407 log.info("Failed to run commands. errors are {}", StrUtils.join(errs, ','));
408 resp.add(CommandOperation.ERR_MSGS, errs);
409 return;
410 }
411
412 SolrResourceLoader loader = req.getCore().getResourceLoader();
413 if (loader instanceof ZkSolrResourceLoader) {
414 int latestVersion = ZkController.persistConfigResourceToZooKeeper((ZkSolrResourceLoader) loader, overlay.getZnodeVersion(),
415 ConfigOverlay.RESOURCE_NAME, overlay.toByteArray(), true);
416 log.info("Executed config commands successfully and persisted to ZK {}", ops);
417 waitForAllReplicasState(req.getCore().getCoreDescriptor().getCloudDescriptor().getCollectionName(),
418 req.getCore().getCoreDescriptor().getCoreContainer().getZkController(),
419 ConfigOverlay.NAME,
420 latestVersion, 30);
421 } else {
422 SolrResourceLoader.persistConfLocally(loader, ConfigOverlay.RESOURCE_NAME, overlay.toByteArray());
423 req.getCore().getCoreDescriptor().getCoreContainer().reload(req.getCore().getName());
424 log.info("Executed config commands successfully and persited to File System {}", ops);
425 }
426
427 }
428
429 private ConfigOverlay deleteNamedComponent(CommandOperation op, ConfigOverlay overlay, String typ) {
430 String name = op.getStr(CommandOperation.ROOT_OBJ);
431 if (op.hasError()) return overlay;
432 if (overlay.getNamedPlugins(typ).containsKey(name)) {
433 return overlay.deleteNamedPlugin(name, typ);
434 } else {
435 op.addError(formatString("NO such {0} ''{1}'' ", typ, name));
436 return overlay;
437 }
438 }
439
440 private ConfigOverlay updateNamedPlugin(SolrConfig.SolrPluginInfo info, CommandOperation op, ConfigOverlay overlay, boolean isCeate) {
441 String name = op.getStr(NAME);
442 String clz = info.options.contains(REQUIRE_CLASS) ? op.getStr(CLASS_NAME) : op.getStr(CLASS_NAME, null);
443 op.getMap(PluginInfo.DEFAULTS, null);
444 op.getMap(PluginInfo.INVARIANTS, null);
445 op.getMap(PluginInfo.APPENDS, null);
446 if (op.hasError()) return overlay;
447 if (!verifyClass(op, clz, info.clazz)) return overlay;
448 if (pluginExists(info, overlay, name)) {
449 if (isCeate) {
450 op.addError(formatString(" ''{0}'' already exists . Do an ''{1}'' , if you want to change it ", name, "update-" + info.getTagCleanLower()));
451 return overlay;
452 } else {
453 return overlay.addNamedPlugin(op.getDataMap(), info.getCleanTag());
454 }
455 } else {
456 if (isCeate) {
457 return overlay.addNamedPlugin(op.getDataMap(), info.getCleanTag());
458 } else {
459 op.addError(formatString(" ''{0}'' does not exist . Do an ''{1}'' , if you want to create it ", name, "create-" + info.getTagCleanLower()));
460 return overlay;
461 }
462 }
463 }
464
465 private boolean pluginExists(SolrConfig.SolrPluginInfo info, ConfigOverlay overlay, String name) {
466 List<PluginInfo> l = req.getCore().getSolrConfig().getPluginInfos(info.clazz.getName());
467 for (PluginInfo pluginInfo : l) if(name.equals( pluginInfo.name)) return true;
468 return overlay.getNamedPlugins(info.getCleanTag()).containsKey(name);
469 }
470
471 private boolean verifyClass(CommandOperation op, String clz, Class expected) {
472 if (clz == null) return true;
473 if (!"true".equals(String.valueOf(op.getStr("runtimeLib", null)))) {
474
475 try {
476 req.getCore().createInitInstance(new PluginInfo(SolrRequestHandler.TYPE, op.getDataMap()), expected, clz, "");
477 } catch (Exception e) {
478 op.addError(e.getMessage());
479 return false;
480 }
481
482 }
483 return true;
484 }
485
486 private ConfigOverlay applySetUserProp(CommandOperation op, ConfigOverlay overlay) {
487 Map<String, Object> m = op.getDataMap();
488 if (op.hasError()) return overlay;
489 for (Map.Entry<String, Object> e : m.entrySet()) {
490 String name = e.getKey();
491 Object val = e.getValue();
492 overlay = overlay.setUserProperty(name, val);
493 }
494 return overlay;
495 }
496
497 private ConfigOverlay applyUnsetUserProp(CommandOperation op, ConfigOverlay overlay) {
498 List<String> name = op.getStrs(CommandOperation.ROOT_OBJ);
499 if (op.hasError()) return overlay;
500 for (String o : name) {
501 if (!overlay.getUserProps().containsKey(o)) {
502 op.addError(formatString("No such property ''{0}''", name));
503 } else {
504 overlay = overlay.unsetUserProperty(o);
505 }
506 }
507 return overlay;
508 }
509
510
511 private ConfigOverlay applyUnset(CommandOperation op, ConfigOverlay overlay) {
512 List<String> name = op.getStrs(CommandOperation.ROOT_OBJ);
513 if (op.hasError()) return overlay;
514
515 for (String o : name) {
516 if (!ConfigOverlay.isEditableProp(o, false, null)) {
517 op.addError(formatString(NOT_EDITABLE, name));
518 } else {
519 overlay = overlay.unsetProperty(o);
520 }
521 }
522 return overlay;
523 }
524
525 private ConfigOverlay applySetProp(CommandOperation op, ConfigOverlay overlay) {
526 Map<String, Object> m = op.getDataMap();
527 if (op.hasError()) return overlay;
528 for (Map.Entry<String, Object> e : m.entrySet()) {
529 String name = e.getKey();
530 Object val = e.getValue();
531 Class typ = ConfigOverlay.checkEditable(name, false, null);
532 if (typ == null) {
533 op.addError(formatString(NOT_EDITABLE, name));
534 continue;
535 }
536
537 if (val != null) {
538 if (typ == String.class) val = val.toString();
539 String typeErr = "Property {0} must be of {1} type ";
540 if (typ == Boolean.class) {
541 try {
542 val = Boolean.parseBoolean(val.toString());
543 } catch (Exception exp) {
544 op.addError(formatString(typeErr, name, typ.getSimpleName()));
545 continue;
546 }
547 } else if (typ == Integer.class) {
548 try {
549 val = Integer.parseInt(val.toString());
550 } catch (Exception exp) {
551 op.addError(formatString(typeErr, typ.getSimpleName()));
552 continue;
553 }
554
555 } else if (typ == Float.class) {
556 try {
557 val = Float.parseFloat(val.toString());
558 } catch (Exception exp) {
559 op.addError(formatString(typeErr, typ.getSimpleName()));
560 continue;
561 }
562
563 }
564 }
565
566
567 overlay = overlay.setProperty(name, val);
568 }
569 return overlay;
570 }
571
572 }
573
574 public static String validateName(String s) {
575 for (int i = 0; i < s.length(); i++) {
576 char c = s.charAt(i);
577 if ((c >= 'A' && c <= 'Z') ||
578 (c >= 'a' && c <= 'z') ||
579 (c >= '0' && c <= '9') ||
580 c == '_' ||
581 c == '-' ||
582 c == '.'
583 ) continue;
584 else {
585 return formatString("''{0}'' name should only have chars [a-zA-Z_-.0-9] ", s);
586 }
587 }
588 return null;
589 }
590
591 public static void setWt(SolrQueryRequest req, String wt) {
592 SolrParams params = req.getParams();
593 if (params.get(CommonParams.WT) != null) return;
594 Map<String, String> map = new HashMap<>(1);
595 map.put(CommonParams.WT, wt);
596 map.put("indent", "true");
597 req.setParams(SolrParams.wrapDefaults(params, new MapSolrParams(map)));
598 }
599
600 @Override
601 public SolrRequestHandler getSubHandler(String path) {
602 if (subPaths.contains(path)) return this;
603 if (path.startsWith("/params/")) return this;
604 return null;
605 }
606
607
608 private static Set<String> subPaths = new HashSet<>(Arrays.asList("/overlay", "/params", "/updateHandler",
609 "/query", "/jmx", "/requestDispatcher", "/znodeVersion"));
610
611 static {
612 for (SolrConfig.SolrPluginInfo solrPluginInfo : SolrConfig.plugins)
613 subPaths.add("/" + solrPluginInfo.getCleanTag());
614
615 }
616
617
618
619
620 @Override
621 public String getDescription() {
622 return "Edit solrconfig.xml";
623 }
624
625
626 @Override
627 public String getVersion() {
628 return getClass().getPackage().getSpecificationVersion();
629 }
630
631 @Override
632 public Category getCategory() {
633 return Category.OTHER;
634 }
635
636
637 public static final String SET_PROPERTY = "set-property";
638 public static final String UNSET_PROPERTY = "unset-property";
639 public static final String SET_USER_PROPERTY = "set-user-property";
640 public static final String UNSET_USER_PROPERTY = "unset-user-property";
641 public static final String SET = "set";
642 public static final String UPDATE = "update";
643 public static final String CREATE = "create";
644 private static Set<String> cmdPrefixes = ImmutableSet.of(CREATE, UPDATE, "delete", "add");
645
646
647
648
649
650 private static void waitForAllReplicasState(String collection,
651 ZkController zkController,
652 String prop,
653 int expectedVersion,
654 int maxWaitSecs) {
655 final RTimer timer = new RTimer();
656
657 List<PerReplicaCallable> concurrentTasks = new ArrayList<>();
658
659 for (String coreUrl : getActiveReplicaCoreUrls(zkController, collection)) {
660 PerReplicaCallable e = new PerReplicaCallable(coreUrl, prop, expectedVersion, maxWaitSecs);
661 concurrentTasks.add(e);
662 }
663 if (concurrentTasks.isEmpty()) return;
664
665 log.info(formatString("Waiting up to {0} secs for {1} replicas to set the property {2} to be of version {3} for collection {4}",
666 maxWaitSecs, concurrentTasks.size(), prop, expectedVersion, collection));
667
668
669 int poolSize = Math.min(concurrentTasks.size(), 10);
670 ExecutorService parallelExecutor =
671 ExecutorUtil.newMDCAwareFixedThreadPool(poolSize, new DefaultSolrThreadFactory("solrHandlerExecutor"));
672 try {
673 List<Future<Boolean>> results =
674 parallelExecutor.invokeAll(concurrentTasks, maxWaitSecs, TimeUnit.SECONDS);
675
676
677 List<String> failedList = null;
678 for (int f = 0; f < results.size(); f++) {
679 Boolean success = false;
680 Future<Boolean> next = results.get(f);
681 if (next.isDone() && !next.isCancelled()) {
682
683 try {
684 success = next.get();
685 } catch (ExecutionException e) {
686
687 }
688 }
689
690 if (!success) {
691 String coreUrl = concurrentTasks.get(f).coreUrl;
692 log.warn("Core " + coreUrl + "could not get the expected version " + expectedVersion);
693 if (failedList == null) failedList = new ArrayList<>();
694 failedList.add(coreUrl);
695 }
696 }
697
698
699 if (failedList != null)
700 throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
701 formatString("{0} out of {1} the property {2} to be of version {3} within {4} seconds! Failed cores: {5}",
702 failedList.size(), concurrentTasks.size() + 1, prop, expectedVersion, maxWaitSecs, failedList));
703
704 } catch (InterruptedException ie) {
705 log.warn(formatString(
706 "Core was interrupted . trying to set the property {1} to version {2} to propagate to {3} replicas for collection {4}",
707 prop, expectedVersion, concurrentTasks.size(), collection));
708 Thread.currentThread().interrupt();
709 } finally {
710 ExecutorUtil.shutdownAndAwaitTermination(parallelExecutor);
711 }
712
713 log.info("Took {}ms to set the property {} to be of version {} for collection {}",
714 timer.getTime(), prop, expectedVersion, collection);
715 }
716
717 public static List<String> getActiveReplicaCoreUrls(ZkController zkController,
718 String collection) {
719 List<String> activeReplicaCoreUrls = new ArrayList<>();
720 ClusterState clusterState = zkController.getZkStateReader().getClusterState();
721 Set<String> liveNodes = clusterState.getLiveNodes();
722 Collection<Slice> activeSlices = clusterState.getActiveSlices(collection);
723 if (activeSlices != null && activeSlices.size() > 0) {
724 for (Slice next : activeSlices) {
725 Map<String, Replica> replicasMap = next.getReplicasMap();
726 if (replicasMap != null) {
727 for (Map.Entry<String, Replica> entry : replicasMap.entrySet()) {
728 Replica replica = entry.getValue();
729 if (replica.getState() == Replica.State.ACTIVE && liveNodes.contains(replica.getNodeName())) {
730 activeReplicaCoreUrls.add(replica.getCoreUrl());
731 }
732 }
733 }
734 }
735 }
736 return activeReplicaCoreUrls;
737 }
738
739 private static class PerReplicaCallable extends SolrRequest implements Callable<Boolean> {
740 String coreUrl;
741 String prop;
742 int expectedZkVersion;
743 Number remoteVersion = null;
744 int maxWait;
745
746 PerReplicaCallable(String coreUrl, String prop, int expectedZkVersion, int maxWait) {
747 super(METHOD.GET, "/config/" + ZNODEVER);
748 this.coreUrl = coreUrl;
749 this.expectedZkVersion = expectedZkVersion;
750 this.prop = prop;
751 this.maxWait = maxWait;
752 }
753
754 @Override
755 public SolrParams getParams() {
756 return new ModifiableSolrParams()
757 .set(prop, expectedZkVersion)
758 .set(CommonParams.WT, CommonParams.JAVABIN);
759 }
760
761 @Override
762 public Boolean call() throws Exception {
763 final RTimer timer = new RTimer();
764 int attempts = 0;
765 try (HttpSolrClient solr = new HttpSolrClient(coreUrl)) {
766
767 while (true) {
768 try {
769 long timeElapsed = (long) timer.getTime() / 1000;
770 if (timeElapsed >= maxWait) {
771 return false;
772 }
773 log.info("Time elapsed : {} secs, maxWait {}", timeElapsed, maxWait);
774 Thread.sleep(100);
775 NamedList<Object> resp = solr.httpUriRequest(this).future.get();
776 if (resp != null) {
777 Map m = (Map) resp.get(ZNODEVER);
778 if (m != null) {
779 remoteVersion = (Number) m.get(prop);
780 if (remoteVersion != null && remoteVersion.intValue() >= expectedZkVersion) break;
781 }
782 }
783
784 attempts++;
785 log.info(formatString("Could not get expectedVersion {0} from {1} for prop {2} after {3} attempts", expectedZkVersion, coreUrl, prop, attempts));
786 } catch (Exception e) {
787 if (e instanceof InterruptedException) {
788 break;
789 } else {
790 log.warn("Failed to get /schema/zkversion from " + coreUrl + " due to: " + e);
791 }
792 }
793 }
794 }
795 return true;
796 }
797
798 @Override
799 public Collection<ContentStream> getContentStreams() throws IOException {
800 return null;
801 }
802
803 @Override
804 protected SolrResponse createResponse(SolrClient client) {
805 return null;
806 }
807 }
808 }